<?php
namespace Pinenut\Model2;

use Pinenut\Model2;
use Pinenut\Model2\Caching\TagTemplate;

/**
 * Модель контролирующая инвалидацию посредством
 * выставления и сброса зависимостей
 *
 * Зависимости отражаются в контексте выполнения запроса
 *
 * формат тэгов:
 *
 * имя коллекции + идентификатор для конечной сущности
 * имя коллекции + list (для списка без критериев)
 * имя коллекции + list + имя поля (по которому идёт выборка или сортировка)
 * имя коллекции + list + имя поля + хеш значения (для выборок по определённому
 * однозначному значению)
 *
 * V2:
 *
 * На этапе выборок по комбинациям тэгов формируем фильтры-маски
 * На этапе сброса по именившимся полям, достаём фильтры-маски,
 * где участвуют поля и вместе со значениями других полей генерим тэги на сброс
 *
 *
 *
 * @author kosh
 *        
 */
class Caching extends Model2 {

	/**
	 * Генерим зависимости по критериям и условиям сортировки
	 *
	 * Чем точней сформулирована зависимость на основе запроса, тем эффективней
	 * будет кеширование, т.к. меньше будет сбросов
	 *
	 * TODO: решить проблему со слишком большим кол-вом комбинаций,
	 * можно выносить множественные поля из комбинации,
	 * это снизит эффективность но и у меньши кол-во тэгов
	 *
	 * @param Finder $finder        	
	 */
	static protected function onBeforeFetch(Finder &$finder) {
		parent::onBeforeFetch($finder);
		
		// пробегаемся по списку полей выборки
		// вытаскиваем те условия, которые конкретные (eq, in, all) - со значениями
		// вытаскиваем те условия, которые не общие ( больше, меньше, neq и прочие),
		// они идут без значений, а так же поля для сортировки
		
		// по этому списку добавляем фильтры в список фильтров заменяя значения маркерами
		// по этому списку в цикле, раскрывая каждое генерим тучу тэгов, должны быть
		// все комбинации значений.
		
// 		// TODO: отсортировать

		$template = new TagTemplate(static::getCollectionName());
		$template->processFinder($finder);
		$template->save();
		$tags = $template->generateTags();
		
// 		\Log::warn('tags-count', count($tags));
// 		\Log::warn('tags', $tags);
		
		if (null !== \Dispatcher::$currentContext) {
			\Dispatcher::$currentContext->addTags($tags);
		}
	}

	/**
	 * Тут нужно нагенерить тэгов сущностям
	 *
	 * В примитивном случае - это просто ид-сущности
	 *
	 * @param unknown $raw        	
	 * @param Finder $finder        	
	 */
	protected static function onAfterFetch(&$raw, Finder &$finder) {
		$namespace = static::getCollectionName();
		$fields = $finder->getFields();
		if (count($fields)) {
			$tags = [];
			foreach (array_values($fields) as $field) {
			$tags = array_merge($tags, array_values(
				array_map(
					function ($v) use($namespace, $field) {
						return $namespace . '_item_' . $v['_id']. '_' . $field;
					}, $raw)));
			}
		} else {
			$tags = array_values(
				array_map(
					function ($v) use($namespace) {
						return $namespace . '_item_' . $v['_id'];
					}, $raw));
		}
		// \Log::warn('tags-count-after', count($tags));
//		\Log::warn('tags-after', $tags);
		if (null !== \Dispatcher::$currentContext) {
			\Dispatcher::$currentContext->addTags($tags);
		}
		parent::onAfterFetch($raw, $finder);
	}
	
	/**
	 * Собираем зависимости и отправляем их в кеширующий контекст
	 *
	 * 1) определяем по апдейтеру какие поля поменялись и достаём массив шаблонов
	 * 		?? нужно ли доставать вложенные поля например поменялся весь поддокумент,
	 * 			а шаблон есть для одного из его полей - судя по всему надо на этапе 
	 * 			сохранения для глубоких полей cохранять и в родительские поля
	 * 2) достаём сырые данные и для каждого шаблона по этим данным формируем массив 
	 * 		тэгов (не актуально для инсерта)
	 * 3) накатываем изменения на сырые данных и для каждого фильтра формируем 
	 * 		массив тэгов  (не актуально для удаления)
	 *
	 * @see \Pinenut\Model2::onBeforeExecute()
	 */
	
	
	static protected function onBeforeExecute(Updater &$updater) {
		$namespace = static::getCollectionName();
		
		$adapter = static::getAdapter();
		
		// вычисляем список полей, для которых нужны фильтры
		$fields = TagTemplate::getChangedFields($updater);
		
		$changeTemplate = new TagTemplate();
		$changeTemplate->setFields($fields);
		
//		\Log::error('onBeforeExecute::fields', $fields);
		$templates = TagTemplate::load(array_keys($fields), $namespace);
		
		// достаём старые данные
		if ($updater->isInsert()) {
			// новая запись
			$raws = [[]];
			
			//Для корректной отработки кейса поискал-и-создал-если-не-нашел
				
			$fetchTags = [$namespace . '_item_' . $updater->getModel()];
//			\Log::warn('tags-onCreate', $fetchTags);
			if (null !== \Dispatcher::$currentContext) {
				\Dispatcher::$currentContext->addTags($fetchTags);
			}

		} else {
			// достаём сущности (сырые), селектом
			$raws = $adapter->select($updater->getWhere());
				
			if (empty($raws)) {
				parent::onBeforeExecute($updater);
				return;
			}
		}
		$tags = [];

		// для каждой записи, которую модифицируем
		foreach ($raws as $row) {
			// для каждого шаблона
			foreach ($templates as $template) {
				$template->processData($row);
				$oldTags = $template->generateTags();
				
				$template->processDataWithUpdater($row, $updater);
				$newTags = $template->generateTags();
				
				$tags = array_merge($tags, (array)$oldTags, (array)$newTags);
			}
			
			
			// не забываем про каждую сущность
			if (isset($row['_id'])) {
				$tags[] = $namespace . '_item_' . $row['_id'];
				$changeTemplate->processDataWithUpdater($row, $updater);
				foreach ($fields as $field => $t) {
					// проверяем - действительно ли изменилась сущность
					if ($changeTemplate->isChanged($field)) {
						$tags[] = $namespace . '_item_' . $row['_id'] . '_' . $field;
					}
				}
			}
		}
		
		

		// уникальный массив, отправляем его на сброс
		$tags = array_unique($tags);
		
		\Log::error('clear-tags', $tags);
		
		// а вот хуй, убиваю сразу
		\ActionCache::clean($tags);
		
		parent::onBeforeExecute($updater);
	}
}